<?php
/**
 * Created by PhpStorm.
 * User: albert
 * Date: 17-12-27
 * Time: 下午4:45
 */

namespace apis\base\models;

use Firebase\JWT\JWT;
use Yii;
use yii\swoole\base\BaseModel;
use yii\swoole\helpers\CoroHelper;
use yii\swoole\web\UserTrait;
use yii\web\IdentityInterface;
use yii\web\Request;

class User extends BaseModel implements IdentityInterface
{
    use UserTrait;

    private static $service = ['user' => ['user', 'UserLogic', 'user.cache.', 'id']];

    private static $curService = [];

    public function __construct(array $config = [], string $type = null)
    {
        parent::__construct($config);
        if ($type !== null) {
            self::$curService[CoroHelper::getId()] = self::$service[$type];
        }
    }

    // Override this method
    protected static function getSecretKey()
    {
        return 'r6eXITd6';
    }

    public function getJWT($header = [])
    {
        // Collect all the data
        $secret = static::getSecretKey();
        $currentTime = time();
        $request = Yii::$app->request;
        $hostInfo = '';

        // There is also a \yii\console\Request that doesn't have this property
        if ($request instanceof Request) {
            $hostInfo = $request->hostInfo;
        }

        // Merge token with presets not to miss any params in custom
        // configuration
        $token = array_merge([
            'iss' => $hostInfo,
            'aud' => $hostInfo,
            'iat' => $currentTime,
            'nbf' => $currentTime
        ], $header);

        // Set up id
        $token['jti'] = $this->getJTI();

        return JWT::encode($token, $secret, static::getAlgo());
    }

    /**
     * Finds an identity by the given ID.
     * @param string|int $id the ID to be looked for
     * @return IdentityInterface the identity object that matches the given ID.
     * Null should be returned if such an identity cannot be found
     * or the identity is not in an active state (disabled, deleted, etc.)
     */
    public static function findIdentity($id)
    {
        $type = Yii::$app->request->headers->get('UserType');
        if (!isset(self::$service[$type])) {
            return null;
        }
        $cid = CoroHelper::getId();
        self::$curService[$cid] = self::$service[$type];
        return static::findByJTI($id);
    }

    public static function findIdentityByAccessToken($token, $type = null)
    {
        $cid = CoroHelper::getId();
        $secret = static::getSecretKey();

        // Decode token and transform it into array.
        // Firebase\JWT\JWT throws exception if token can not be decoded
        try {
            $decoded = JWT::decode($token, $secret, [static::getAlgo()]);
        } catch (\Exception $e) {
            return false;
        }

        static::$decodedToken[$cid] = (array)$decoded;

        // If there's no jti param - exception
        if (!isset(static::$decodedToken[$cid]['jti'])) {
            return false;
        }

        if (isset(static::$decodedToken[$cid]['ut'])) {
            self::$curService[$cid] = self::$service[static::$decodedToken[$cid]['ut']];
        }

        // JTI is unique identifier of user.
        // For more details: https://tools.ietf.org/html/rfc7519#section-4.1.7
        $id = static::$decodedToken[$cid]['jti'];
        return static::findByJTI($id);
    }

    public static function findByJTI($id)
    {
        $cid = CoroHelper::getId();
        list($service, $route, $cacheKey, $priKey) = self::$curService[$cid];
        $data = Yii::$app->usercache->get($cacheKey . $id);
        if (!$data) {
            $data = Yii::$app->rpc->call($service, $route)->findOne(['id' => $id]);
            Yii::$app->usercache->set($cacheKey . $id, $data);
        }
        return $data ? new static($data) : null;
    }

    /**
     * Returns an ID that can uniquely identify a user identity.
     * @return string|int an ID that uniquely identifies a user identity.
     */
    public function getId()
    {
        $cid = CoroHelper::getId();
        $key = self::$curService[$cid][count(self::$curService[$cid]) - 1];
        return isset($this->_attributes[$key]) ? $this->_attributes[$key] : null;
    }

    /**
     * Returns a key that can be used to check the validity of a given identity ID.
     *
     * The key should be unique for each individual user, and should be persistent
     * so that it can be used to check the validity of the user identity.
     *
     * The space of such keys should be big enough to defeat potential identity attacks.
     *
     * This is required if [[User::enableAutoLogin]] is enabled.
     * @return string a key that is used to check the validity of a given identity ID.
     * @see validateAuthKey()
     */
    public function getAuthKey()
    {
        return static::getJWT();
    }

    public function validatePassword($password)
    {
        return $this->password === md5($password);
    }

    /**
     * Validates the given auth key.
     *
     * This is required if [[User::enableAutoLogin]] is enabled.
     * @param string $authKey the given auth key
     * @return bool whether the given auth key is valid.
     * @see getAuthKey()
     */
    public function validateAuthKey($authKey)
    {
        return $authKey === $this->getAuthKey();
    }
}